package org.apache.tinkerpop.gremlin.server.channel;

import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.apache.tinkerpop.gremlin.server.AbstractGraphChannelizer;
import org.apache.tinkerpop.gremlin.server.Settings;
import org.apache.tinkerpop.gremlin.server.auth.AllowAllAuthenticator;
import org.apache.tinkerpop.gremlin.server.handler.AbstractAuthenticationHandler;
import org.apache.tinkerpop.gremlin.server.handler.HttpBasicAuthenticationHandler;
import org.apache.tinkerpop.gremlin.server.handler.HttpBasicAuthorizationHandler;
import org.apache.tinkerpop.gremlin.server.handler.HttpGremlinEndpointHandler;
import org.apache.tinkerpop.gremlin.server.util.GraphServerGremlinExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GraphHttpChannelizer extends AbstractGraphChannelizer {
  private static final Logger logger = LoggerFactory.getLogger(GraphHttpChannelizer.class);

  private HttpGremlinEndpointHandler httpGremlinEndpointHandler;

  @Override
  public void init(final GraphServerGremlinExecutor graphServerGremlinExecutor) {
    super.init(graphServerGremlinExecutor);
    httpGremlinEndpointHandler =
        new HttpGremlinEndpointHandler(serializers, gremlinExecutor, graphManager, settings);
  }

  @Override
  public void configure(final ChannelPipeline pipeline) {
    if (logger.isDebugEnabled()) {
      pipeline.addLast(new LoggingHandler("log-io", LogLevel.DEBUG));
    }

    pipeline.addLast("http-server", new HttpServerCodec());

    if (logger.isDebugEnabled()) {
      pipeline.addLast(new LoggingHandler("http-io", LogLevel.DEBUG));
    }

    final HttpObjectAggregator aggregator = new HttpObjectAggregator(settings.maxContentLength);
    aggregator.setMaxCumulationBufferComponents(settings.maxAccumulationBufferComponents);
    pipeline.addLast(PIPELINE_HTTP_AGGREGATOR, aggregator);

    if (authenticator != null) {
      // Cannot add the same handler instance multiple times unless
      // it is marked as @Sharable, indicating a race condition will
      // not occur. It may not be a safe assumption that the handler
      // is sharable so create a new handler each time.
      final AbstractAuthenticationHandler authenticationHandler =
          authenticator.getClass() == AllowAllAuthenticator.class
              ? null
              : instantiateAuthenticationHandler(settings);
      if (authenticationHandler != null) {
        pipeline.addLast(PIPELINE_AUTHENTICATOR, authenticationHandler);
      }
    }

    if (authorizer != null) {
      final ChannelInboundHandlerAdapter authorizationHandler =
          new HttpBasicAuthorizationHandler(authorizer);
      pipeline.addLast(PIPELINE_AUTHORIZER, authorizationHandler);
    }

    pipeline.addLast("http-gremlin-handler", httpGremlinEndpointHandler);
  }

  private AbstractAuthenticationHandler instantiateAuthenticationHandler(final Settings settings) {
    final String authHandlerClass = settings.authentication.authenticationHandler;
    if (authHandlerClass == null) {
      // Keep things backwards compatible
      return new HttpBasicAuthenticationHandler(authenticator, settings);
    } else {
      return createAuthenticationHandler(settings);
    }
  }

  @Override
  public void finalize(final ChannelPipeline pipeline) {
    pipeline.remove(PIPELINE_OP_SELECTOR);
    pipeline.remove(PIPELINE_OP_EXECUTOR);
  }
}
